// Copyright (C) 2023 即时通讯网(52im.net) & Jack Jiang.
// The RainbowChat Project. All rights reserved.
// 
// 【本产品为著作权产品，合法授权后请放心使用，禁止外传！】
// 【本次授权给：<北京小羊驼科技有限公司>，授权编号：<NT231212144350>，代码指纹：<A.702363430.550>，技术对接人微信：<ID: wxid_wbyootvkdcgj22>】
// 【授权寄送：<收件：苏先生、地址：北京市大兴区北京密码西区6号楼1单元301、电话：18613885610、邮箱：bd@huiyuanxiang-inc.com>】
// 
// 【本系列产品在国家版权局的著作权登记信息如下】：
// 1）国家版权局登记名(简称)和权证号：RainbowChat    （证书号：软著登字第1220494号、登记号：2016SR041877）
// 2）国家版权局登记名(简称)和权证号：RainbowChat-Web（证书号：软著登字第3743440号、登记号：2019SR0322683）
// 3）国家版权局登记名(简称)和权证号：RainbowAV      （证书号：软著登字第2262004号、登记号：2017SR676720）
// 4）国家版权局登记名(简称)和权证号：MobileIMSDK-Web（证书号：软著登字第2262073号、登记号：2017SR676789）
// 5）国家版权局登记名(简称)和权证号：MobileIMSDK    （证书号：软著登字第1220581号、登记号：2016SR041964）
// * 著作权所有人：江顺/苏州网际时代信息科技有限公司
// 
// 【违法或违规使用投诉和举报方式】：
// 联系邮件：jack.jiang@52im.net
// 联系微信：hellojackjiang
// 联系QQ号：413980957
// 授权说明：http://www.52im.net/thread-1115-1-1.html
// 官方社区：http://www.52im.net
package com.eva.framework;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.eva.epc.common.util.CommonUtils;
import com.eva.framework.dto.DataFromClient;
import com.eva.framework.dto.DataFromServer;
import com.eva.framework.utils.EndsExceptionFactory;
import com.eva.framework.utils.LoggerFactory;

/**
 * 服务端http框架的MVC控制器父类，用于捕获客客户端的http请求并调
 * 度业务处理器（Processor），遵从MVC框架设计原则。
 * <p>
 * <b>MVC调用层级: </b>
 * {@link #service(HttpServletRequest, HttpServletResponse)}
 *   -> {@link #process(HttpServletRequest, HttpServletResponse)}
 *   -> {@link #dispatch(DataFromClient, HttpServletRequest, HttpServletResponse)}.
 * <p>
 * <b>MVC调用原理: </b>
 * <pre>
 * -------------------------------------------- MVC框架的HTTP处理逻辑流程 ----------------------------------------------
 * http请求 => HttpPortal子类(MVC控制器) -> Processor子类(处理器) -> JobDispatcher方法(作业调度器) -> Action代码段(动作)
 *                                               |                        |                                |
 *                                         [processor_id]         [job_dispatch_id]                   [action_id]
 * --------------------------------------------------------------------------------------------------------------------
 * </pre>
 * 
 * @author Jack Jiang(http://www.52im.net/space-uid-1.html)
 * @version 1.0
 */
public abstract class HttpController extends HttpServlet
{
	private static final long serialVersionUID = 1L;
	
	/** 本项目部置到服务端上去后的物理磁盘根目录 */
	public static String SERVER_PATH = "";
	  
	/**
	 * 本Servlet实例化时的初始化方法.
	 *
	 * @throws ServletException
	 */
	@Override public void init(ServletConfig config) throws ServletException
	{
		SERVER_PATH = config.getServletContext().getRealPath("/");
		super.init(config);
		LoggerFactory.getLog().info("Hello EVA.EPC,init ok... ");
	}

	/**
	 * 响应客端请求的主方法(本方法由Web容器调用).
	 *
	 * @param req HttpServletRequest
	 * @param res HttpServletResponse
	 * @throws ServletException
	 * @throws IOException
	 * @see HttpServlet
	 */
	@Override public void service(HttpServletRequest req, HttpServletResponse res) throws
		ServletException, IOException
	{
		try
		{
			//调用核心处理方法
			process(req,res);
		}
		catch (Exception e)
		{
//			e.printStackTrace();
//			throw new RuntimeException(e.getMessage());
		}
	}

	/**
	 * 核心处理方法.<br>
	 * <p>
	 * 本方法会确保处理完成后写回一个DataFromServer对象（客户端通过它可知是否处理成功等信息）给客端，如果处理成功则
	 * DataFromServer对象中的sucess字值会被设置成true且returnValue会设置成处理完成的返回的对象，否则sucess将被设置成
	 * false且returnValue里存放的将是错误消息文本（该文本不能保证一定不是null）.
	 * </p>
	 * <pre>
	 * 	注：子类的处理实现推荐overload方法processImpl(DataFromClient, HttpServletRequest, HttpServletResponse)
	 * ，一般情况下不推荐重写本方法.
	 * </pre>
	 * 
	 * @param req HttpServletRequest 客户端请求HttpServletRequest对象
	 * @param res HttpServletResponse 客户端响应HttpServletResponse对象
	 * @see #dispatch(DataFromClient, HttpServletRequest, HttpServletResponse)
	 * @see DataFromServer
	 * @throws Exception 本方法调用中出现任何异常时都会抛出
	 */
	protected void process(HttpServletRequest req,HttpServletResponse res) throws Exception
	{
		//服务端是否要写回数据给客户端（本字段对应于DataFromClient中的doInput字段并且意义一样）
		boolean needRecieve = true;
		
		//处理完成后将要返回给客端的数据对象（通过它可知是否处理成功等信息）
		DataFromServer objFromServer = new DataFromServer();
		try
		{
			LoggerFactory.getLog().debug(">>S 客户端Cookie="+req.getRequestedSessionId() +" ip="+req.getRemoteHost()+":"+req.getRemotePort());
			//第1/3步：读取PC客户端发过来的数据
			DataFromClient dataFromClient = (DataFromClient)recieveFromClient(req);
			//* 服务端是否要写回数据给客户端
			needRecieve = dataFromClient.isDoInput();
			
			//第2/3步：提交处理器进行业务处理
			Object processedData = dispatch(dataFromClient,req,res);
			//处理成功了
			objFromServer.setSuccess(true);
			objFromServer.setReturnValue(processedData);
		}
		catch (EndsExceptionFactory.EInvalidTokenException e0)
		{
			objFromServer.setSuccess(false);
			// 针对无法的token单独设置返回码，便于客户端鉴别
			objFromServer.setCode(DataFromServer.CODE_INVALID_TOKEN);
			// 2014-05-05日邮js改成只返回简单错误信息，而非完整Exception对象，目的是在跨平台实现时反射成JSON时不会StackOverflowError
			objFromServer.setReturnValue(CommonUtils.getExceptionStackTraceForPlainText(e0));//e);
			LoggerFactory.getLog().warn(e0.getMessage(), e0);
			throw e0;
		}
		catch (Exception e)
		{
			objFromServer.setSuccess(false);
			// 2014-05-05日邮js改成只返回简单错误信息，而非完整Exception对象，目的是在跨平台实现时反射成JSON时不会StackOverflowError
			objFromServer.setReturnValue(CommonUtils.getExceptionStackTraceForPlainText(e));//e);
			LoggerFactory.getLog().warn(e.getMessage(), e);
			throw e;
		}
		finally
		{
			//* 服务端需要写回数据给客户端
			if(needRecieve)
			{
				//第3/3步：将处理完成的数据对象发回给客户端
				sendToClient(res,objFromServer);
			}
			LoggerFactory.getLog().debug(">>E 处理完成,客户端"+ " ip="+req.getRemoteHost()+":"+req.getRemotePort()
					+" 的请处理成功了?"+objFromServer.isSuccess()+(needRecieve?
							" - ret="+objFromServer.getReturnValue().getClass()//objFromServer.getReturnValue() 直接输出返回值用于早期MVC框架调试时，生产环境下将大幅影响系统性能
							:" - [doInput=false,无需返回值给客户端]"));
		}
	}
	
	/**
	 * 将数据对象发回给PC客户端.
	 * @param res 客户端响应HttpServletResponse对象
	 * @param objFromServer 要发送的可系列化的数据对象
	 * @throws Exception 如果在操作OutputStream时出现异常
	 */
	protected void sendToClient(HttpServletResponse res,DataFromServer objFromServer)
		throws IOException
	{
		ObjectOutputStream objOut=new ObjectOutputStream(res.getOutputStream());
		objOut.writeObject(objFromServer);
		objOut.flush();
		objOut.close();
	}
	
	/**
	 * 读取PC客户端传过来的数据对象。
	 * @param req 客户端请求HttpServletRequest对象
	 * @return 返回读取出来的客户端数据对象
	 * @throws IOException 如果req.getInputStream()出现异常时抛出
	 * @throws ClassNotFoundException 如果is.readObject()出现异常时抛出
	 */
	protected DataFromClient recieveFromClient(HttpServletRequest req)
		throws IOException, ClassNotFoundException
	{
		ObjectInputStream is=new ObjectInputStream(req.getInputStream());
		DataFromClient ObjFromClient = (DataFromClient)is.readObject();
		is.close();
		return ObjFromClient;
	}
	
	/**
	 * 客户端的请求处理实现.
	 * <pre>
	 * 	注：子类一般情况只需要重写此类即可达到要求。
	 * </pre>
	 * @param objFromClient 这就是客户传过来的数据对象了（本方法中是必须的参数）
	 * @param req 客户端请求实例引用（把它传过来完全是未了考虑以后可能有其它作用，本方法中它不是必须的）
	 * @param res 客户端响应实例引用（把它传过来完全是未了考虑以后可能有其它作用，本方法中它不是必须的）
	 * @return 处理完返回的对象（要给客户端的）
	 * @throws Exception 本方法调用中出现任何异常时都会抛出
	 */
	protected abstract Object dispatch(DataFromClient objFromClient,HttpServletRequest req
			,HttpServletResponse res) throws Exception;
}
